package com.hexacta.hrs.domain.staffing;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.vectrics.common.db.AbstractRecordObject;
import org.vectrics.employee.Employee;

import com.hexacta.hrs.domain.exchange.Accrued;
import com.hexacta.hrs.domain.exchange.Currency;
import com.hexacta.hrs.domain.exchange.Referring;
import com.hexacta.hrs.domain.staffing.assignment.AssignmentType;
import com.hexacta.hrs.domain.staffing.assignment.EmployeeAssignment;
import com.hexacta.hrs.domain.staffing.project.ContractType;
import com.hexacta.hrs.domain.staffing.relationship.RequirementAssignmentRelationship;
import com.hexacta.hrs.domain.staffing.relationship.generators.AssignmentRelationshipGenerator;

/**
 * Representa un proyecto de la empresa. Los proyectos pueden ser de clientes
 * externos o propios de la empresa (internos).
 * 
 * @author ncortinez
 * 
 */
@SuppressWarnings("unchecked")
public class Project extends AbstractRecordObject {

    private static final long serialVersionUID = 1648863166018586161L;

    private Long id;

    private String name = null;

    private String description = null;

    private Calendar dateStart = null;

    private Calendar dateEnd = null;

    private Client client = null;

    private Employee employeeInCharge = null;

    private ContractType contractType = null;
    
    private Currency currency = null;
    
    private Set<EmployeeAssignment> assignments = new HashSet<EmployeeAssignment>();

    private Set<Requirement> requirements = new HashSet<Requirement>();
    
    private Set<Accrued> accrueds = new HashSet<Accrued>();
    
    private Set<Referring> referrings = new HashSet<Referring>();
    
    // Un proyecto es interno si el cliente es la misma empresa.
    public boolean isInternalProject() {
        return client != null && client.isInternal();
    }

    // Devuelve todos los Assigned Requirements.
    // Assigned Requirement: es el caso en el que una asignacion esta cubriendo
    // un requerimiento de un proyecto.
    public List<RequirementAssignmentRelationship> getAssignedRequirements() {
        AssignmentRelationshipGenerator generator = new AssignmentRelationshipGenerator();
        return generator.getAssignedRequirements(getRequirements(), getAssignmentsExceptAddOnsExcludeTentatives());
    }

    // Devuelve todos los Non Requirement Assignment.
    // Non Requirement Assignment: es el caso en el que un recurso esta asignado
    // a un proyecto, pero no existe un requerimiento que lo concuerde.
    public List<RequirementAssignmentRelationship> getNonRequirementAssignments(final boolean useTentatives) {
        AssignmentRelationshipGenerator generator = new AssignmentRelationshipGenerator();

        if (useTentatives)
            return generator.getNonRequirementAssignments(getRequirements(), getAssignmentsExceptAddOns());
        else
            return generator.getNonRequirementAssignments(getRequirements(),
                    getAssignmentsExceptAddOnsExcludeTentatives());
    }

    // Devuelve todos los Unassigned Requirements.
    // Unassigned Requirement: es el caso en que un requerimiento no es cubierto
    // por una asignacion.
    public List<RequirementAssignmentRelationship> getUnassignedRequirements(final boolean useTentatives) {
        AssignmentRelationshipGenerator generator = new AssignmentRelationshipGenerator();

        if (useTentatives)
            return generator.getUnassignedRequirements(getRequirements(), getAssignmentsExceptAddOns());
        else
            return generator
                    .getUnassignedRequirements(getRequirements(), getAssignmentsExceptAddOnsExcludeTentatives());
    }

    // Devuelve todos los Assigned Requirements, usando las asignaciones
    // tentativas para realizar los calculos.
    // Assigned Requirement: es el caso en el que una asignacion esta cubriendo
    // un requerimiento de un proyecto.
    public List<RequirementAssignmentRelationship> getAssignedRequirementsUsingTentatives() {
        AssignmentRelationshipGenerator generator = new AssignmentRelationshipGenerator();
        return generator.getAssignedRequirements(getRequirements(), getAssignmentsExceptAddOns());
    }

    // Devuelve todos los Non Requirement Assignment, usando las asignaciones
    // tentativas para realizar los calculos.
    // Non Requirement Assignment: es el caso en el que un recurso esta asignado
    // a un proyecto, pero no existe un requerimiento que lo concuerde.
    public List<RequirementAssignmentRelationship> getNonRequirementAssignmentsUsingTentatives() {
        AssignmentRelationshipGenerator generator = new AssignmentRelationshipGenerator();
        return generator.getNonRequirementAssignments(getRequirements(), getAssignmentsExceptAddOns());
    }

    // Devuelve todos los Unassigned Requirements, usando las asignaciones
    // tentativas para realizar los calculos.
    // Unassigned Requirement: es el caso en que un requerimiento no es cubierto
    // por una asignacion.
    public List<RequirementAssignmentRelationship> getUnassignedRequirementsUsingTentatives() {
        AssignmentRelationshipGenerator generator = new AssignmentRelationshipGenerator();
        return generator.getUnassignedRequirements(getRequirements(), getAssignmentsExceptAddOns());
    }

    // Devuelve todas las asignaciones del tipo Assigned (no devuelve
    // asignaciones del tipo Add On).
    public List<EmployeeAssignment> getAssignmentsExceptAddOns() {
        List<EmployeeAssignment> assignmentsExceptAddOns = (List<EmployeeAssignment>) CollectionUtils.select(
                getAssignments(), new Predicate() {
                    @Override
                    public boolean evaluate(final Object arg) {
                        return ((EmployeeAssignment) arg).getAssignmentType().equals(AssignmentType.Assigned);
                    }
                });
        return assignmentsExceptAddOns;
    }

    // Devuelve todas las asignaciones del tipo Assigned (no devuelve
    // asignaciones del tipo Add On).
    public List<EmployeeAssignment> getAssignmentsExceptAddOnsExcludeTentatives() {
        List<EmployeeAssignment> assignmentsExceptAddOns = (List<EmployeeAssignment>) CollectionUtils.select(
                getAssignmentsExceptAddOns(), new Predicate() {
                    @Override
                    public boolean evaluate(final Object arg) {
                        return !((EmployeeAssignment) arg).isTentative();
                    }
                });
        return assignmentsExceptAddOns;
    }

    public List<EmployeeAssignment> getAddOnAssignmentsExcludeTentatives() {
        List<EmployeeAssignment> list = new ArrayList<EmployeeAssignment>();
        list.addAll(getAddOnAvailableAssignments());
        list.addAll(getAddOnNotAvailableAssignments());
        List<EmployeeAssignment> assignmentsAddOns = (List<EmployeeAssignment>) CollectionUtils.select(list,
                new Predicate() {
                    @Override
                    public boolean evaluate(final Object arg) {
                        return !((EmployeeAssignment) arg).isTentative();
                    }
                });
        return assignmentsAddOns;
    }

    // Devuelve todas las asignaciones del tipo Add On.
    public List<EmployeeAssignment> getAddOnAvailableAssignments() {
        List<EmployeeAssignment> addOnAssignments = (List<EmployeeAssignment>) CollectionUtils.select(getAssignments(),
                new Predicate() {
                    @Override
                    public boolean evaluate(final Object arg) {
                        return ((EmployeeAssignment) arg).getAssignmentType().equals(AssignmentType.AddOnAvailable);
                    }
                });
        return addOnAssignments;
    }

    // Devuelve todas las asignaciones del tipo Add On.
    public List<EmployeeAssignment> getAddOnNotAvailableAssignments() {
        List<EmployeeAssignment> addOnAssignments = (List<EmployeeAssignment>) CollectionUtils.select(getAssignments(),
                new Predicate() {
                    @Override
                    public boolean evaluate(final Object arg) {
                        return ((EmployeeAssignment) arg).getAssignmentType().equals(AssignmentType.AddOnNotAvailable);
                    }
                });
        return addOnAssignments;
    }

    public Requirement findRequirement(final Long requirementId) {
        Requirement toReturn = null;
        if (requirementId != null) {
            for (Requirement requirement : requirements) {
                if (requirement.getId().equals(requirementId)) {
                    toReturn = requirement;
                    break;
                }
            }
        }
        return toReturn;
    }
    
    
    public List<EmployeeAssignment> getCurrentlyAssigned(){
    	 List<EmployeeAssignment> current = (List<EmployeeAssignment>) CollectionUtils.select(getAssignments(),
                 new Predicate() {
                     @Override
                     public boolean evaluate(final Object arg) {
                         return ((EmployeeAssignment) arg).getEndDate().after(Calendar.getInstance());
                     }
                 });
         return current;
    }

    // Agrega un requerimiento al proyecto.
    public void addRequirement(final Requirement requirement) {
        getRequirements().add(requirement);
    }

    // Agrega una asignacion de un empleado al proyecto.
    public void addAssignment(final EmployeeAssignment employeeAssignment) {
        employeeAssignment.setProject(this);
        getAssignments().add(employeeAssignment);
    }

    // Elimina una asignacion de las asignacion del proyecto.
    public void removeAssignment(final EmployeeAssignment employeeAssignment) {
        getAssignments().remove(employeeAssignment);
    }

    // Elimina un requerimiento de los requerimientos del proyecto.
    public void removeRequirement(final Requirement requirement) {
        getRequirements().remove(requirement);
    }

    /**
     * GETTERS and SETTERS
     */
    
    public Currency getCurrency() {
		return currency;
	}

	public void setCurrency(Currency currency) {
		this.currency = currency;
	}
    
    public Set<EmployeeAssignment> getAssignments() {
        if (assignments != null) {
            assignments.remove(null);
        }
        return assignments;
    }

	public void setAssignments(final Set<EmployeeAssignment> assignments) {
        this.assignments = assignments;
    }

    public void setRequirements(final Set<Requirement> requirements) {
        this.requirements = requirements;
    }

    public Set<Requirement> getRequirements() {
        if (requirements != null) {
            requirements.remove(null);
        }
        return requirements;
    }
    
	public Set<Accrued> getAccrueds() {
		return accrueds;
	}

	public void setAccrueds(Set<Accrued> accrueds) {
		this.accrueds = accrueds;
	}
	
	public Set<Referring> getReferrings() {
		return referrings;
	}

	public void setReferrings(Set<Referring> referrings) {
		this.referrings = referrings;
	}

	public void setClient(final Client client) {
        this.client = client;
    }

    public Client getClient() {
        return client;
    }

    @Override
    public Long getId() {
        return id;
    }

    @Override
    public void setId(final Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(final String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(final String description) {
        this.description = description;
    }

    public Calendar getDateStart() {
        return dateStart;
    }

    public void setDateStart(final Calendar dateStart) {
        this.dateStart = dateStart;
    }

    public Calendar getDateEnd() {
        return dateEnd;
    }

    public void setDateEnd(final Calendar dateEnd) {
        this.dateEnd = dateEnd;
    }
    
    public String getFormatedStartDate(){
    	SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
    	return sdf.format(dateStart.getTime());
    }
    
    public String getFormatedEndDate(){
    	if (dateEnd != null) {
    		SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
        	return sdf.format(dateEnd.getTime());
		}
    	return "";
    }
    
    public void setEmployeeInCharge(final Employee employeeInCharge) {
        this.employeeInCharge = employeeInCharge;
    }

    public Employee getEmployeeInCharge() {
        return employeeInCharge;
    }
    
    //Este metodo no es utilizado
//    private Calendar getMinCalendar() {
//        Date date = new Date(0);
//        Calendar calendar = Calendar.getInstance();
//        calendar.setTime(date);
//        calendar.set(Calendar.HOUR, 0);
//        calendar.set(Calendar.MINUTE, 0);
//        calendar.set(Calendar.SECOND, 0);
//        calendar.set(Calendar.MILLISECOND, 0);
//
//        return calendar;
//    }

    /**
     * @param contractType
     *            the contractType to set
     */
    public void setContractType(final ContractType contractType) {
        this.contractType = contractType;
    }

    /**
     * @return the contractType
     */
    public ContractType getContractType() {
        return contractType;
    }

	public void addAccrued(Accrued accrued) {
		this.getAccrueds().add(accrued);
	}
	
	public void addAccruedList(List<Accrued> accrueds) {
		this.getAccrueds().addAll(accrueds);
	}
}
